//-------------------------------------------------------------------
//	Copyright (c) 2013-2014  Zhirnov Andrey
//	This file is part of the "UX OpwnGL Framework" project.
//	See copyright notice in "UX_GL_Framework.h".
//-------------------------------------------------------------------

#pragma once

#include "VoxelTerrain.h"

namespace Voxels
{

	//
	// Voxels Compute Pass
	//
	
	template <typename TVoxel, uint VoxelID>
	class CLComputePass
	{
	// types
	public:
		enum EFlags
		{
			READ			= 1 << 0,
			WRITE			= 1 << 1,
			READ_WRITE		= READ | WRITE,
			VOXEL_TYPE_ID	= VoxelID,
		};
		
		typedef TVoxel												voxel_t;
		typedef CLComputePass< TVoxel, VoxelID >					Self;
		typedef Voxels::VoxelTerrain< TVoxel, VoxelID >				VoxelTerrain_t;
		typedef typename VoxelTerrain_t::EProcessingError			EProcessingError;
		
		class IEventListener : protected CReferenced
		{
		public:
			virtual bool OnCreate () = 0;
			virtual void BeforeRunProgram (CLQueue *, CLKernel *, const ivec3 &blockCoord)  = 0;
			virtual void AfterRunProgram (CLQueue *, CLImage *inImage, CLImage *outImage, const ivec3 &blockCoord) = 0;
		};

		SHARED_POINTER( IEventListener );

	private:
		class DefEventListener : public IEventListener
		{
		public:
			bool OnCreate ()														override	{ return true; }
			void BeforeRunProgram (CLQueue *, CLKernel *, const ivec3 &)			override	{}
			void AfterRunProgram (CLQueue *, CLImage *, CLImage *, const ivec3 &)	override	{}
		};


	// variables
	private:
		//CLDevice			_device;
		CLQueue				_queue;

		CLImage				_inImage;
		CLImage				_outImage;

		CLProgramPtr		_prog;
		CLKernel			_kernel;
		
		PerfomanceTimer		_timer;

		double				_processingStartTime;
		double				_processingBlockStartTime;

		ivec3				_currentBlockCoord;
		uint				_iteration;

		uint				_flags;

		IEventListenerPtr	_eventListener;
		IEventListenerPtr	_defaultEventListener;

		bool				_created;


	// methods
	private:
		CLComputePass (const Self &) {}
		void operator = (const Self &) {}

	public:
		CLComputePass ();
		
		bool Create (uni_c_string program, uint flags, uni_c_string options = uni_c_string(), const IEventListenerPtr &listener = null);
		void Destroy ();

		bool IsValid ()				const	{ return _prog.IsNotNull() and _created; }

		void SetIteration (uint iter)		{ _iteration = iter; }

		CLQueue *	GetQueue ()				{ return &_queue; }


		// VoxelTerrain_Compute //
		void operator () (const VoxelsBlockPtr &inBlock, OUT VoxelsBlock &outBlock);

		void Begin ();
		void End ();

		bool BeginBlock (const ivec3 &pos);
		void EndBlock ();
		bool OnError (typename EProcessingError::type code) const;

		bool NeedToLoadBlock ()		const	{ return EnumCmp( _flags, READ ); }
		bool NeedToStoreBlock ()	const	{ return EnumCmp( _flags, WRITE ); }
	};



/*
=================================================
	constructor
=================================================
*/
	template <typename V, uint ID>
	inline CLComputePass<V,ID>::CLComputePass () :
		_iteration(0), _flags(0), _processingStartTime(0.0), _processingBlockStartTime(0.0), _created(false)
	{
		_defaultEventListener = new DefEventListener();
	}
	
/*
=================================================
	Create
=================================================
*/
	template <typename V, uint ID>
	inline bool CLComputePass<V,ID>::Create (uni_c_string programName, uint flags, uni_c_string options, const IEventListenerPtr &listener)
	{
		Destroy();
		/*
		if ( _device.DeviceId() == null )
		{
			CHECK_RETURN( _device.Init() );
		}
		*/
		if ( _queue.Id() == null )
		{
			CHECK_RETURN( _queue.Create() );
		}

		CHECK_RETURN( ShaderLoader::Load( _prog, programName, options ) );
		CHECK_RETURN( _kernel.Create( _prog.ptr(), "Main" ) );

		_eventListener = listener.IsNotNull() ? listener : _defaultEventListener;
		CHECK_RETURN( _eventListener->OnCreate() );
		
		_flags	 = flags;
		_created = true;
		return true;
	}
	
/*
=================================================
	Destroy
=================================================
*/
	template <typename V, uint ID>
	inline void CLComputePass<V,ID>::Destroy ()
	{
		_iteration					= 0;
		_flags						= 0;
		_processingStartTime		= 0.0;
		_processingBlockStartTime	= 0.0;
		_created					= false;
		_prog						= null;
		_currentBlockCoord			= ivec3();
		_eventListener				= null;

		_inImage.Destroy();
		_outImage.Destroy();
		_kernel.Destroy();
	}

/*
=================================================
	operator ()
----
	kernel args:
		read only:  (read_only image2D_t, int iteration, int3 blockCoord, ...)
		write only: (write_only image2D_t, int iteration, int3 blockCoord, ...)
		read, write: (read_only image2D_t, write_only image2D_t, int iteration, int3 blockCoord, ...)
=================================================
*/
	template <typename V, uint ID>
	inline void CLComputePass<V,ID>::operator () (const Voxels::VoxelsBlockPtr &inBlock, OUT Voxels::VoxelsBlock &outBlock)
	{
		FUNC_PROFILER();

		uint		args		= 0;
		CLImage *	read_image	= null;
		CLImage *	write_image = null;


		// setup kernel arguments
		if ( NeedToLoadBlock() )
		{
			read_image = &_inImage;

			CHECK( _inImage.Create2D( inBlock->TextureSize().To<svec2>(), cl_memory::READ_ONLY, inBlock->CLFormat(),
										(void *) inBlock->Data().ptr(), inBlock->GetRowPitch() ) );
			_kernel.SetArg( args++, _inImage.Id() );
		}

		if ( NeedToStoreBlock() )
		{
			write_image = &_outImage;

			CHECK( _outImage.Create2D( outBlock.TextureSize().To<svec2>(), cl_memory::WRITE_ONLY, outBlock.CLFormat() ) );
			_kernel.SetArg( args++, _outImage.Id() );
		}
		
		_kernel.SetArg( args++, _iteration );
		_kernel.SetArg( args++, ivec4(_currentBlockCoord, 0) );


		// run program
		_eventListener->BeforeRunProgram( &_queue, &_kernel, _currentBlockCoord );

		CHECK( _queue.EnqueueNDRange( &_kernel, svec3(0), outBlock.Dimension().To<svec3>(), svec3(1) ) );
		
		// read output
		if ( NeedToStoreBlock() )
		{
			CHECK( _outImage.EnqueueRead( &_queue, true, svec3(0), svec3( outBlock.TextureSize().To<svec2>(), 1 ),
											svec2( outBlock.GetRowPitch(), 0 ),
											outBlock.Data().ptr() ) );
		}
		_eventListener->AfterRunProgram( &_queue, read_image, write_image, _currentBlockCoord );

		CHECK( _queue.Finish() );
	}
	
/*
=================================================
	Begin
=================================================
*/
	template <typename V, uint ID>
	inline void CLComputePass<V,ID>::Begin ()
	{
		_processingStartTime = _timer.GetTime();
	}
	
/*
=================================================
	End
=================================================
*/
	template <typename V, uint ID>
	inline void CLComputePass<V,ID>::End ()
	{
		string	log;
		log << "------------------------------- iteration " << _iteration << " time: ";
		StringUtils::TimeToStr( log, _timer.GetTime() - _processingStartTime );

		DEBUG_CONSOLE( log.cstr() );

		++_iteration;
	}

/*
=================================================
	BeginBlock
=================================================
*/
	template <typename V, uint ID>
	inline bool CLComputePass<V,ID>::BeginBlock (const ivec3 &pos)
	{
		_processingBlockStartTime	= _timer.GetTime();
		_currentBlockCoord			= pos;
		return true;
	}

/*
=================================================
	EndBlock
=================================================
*/
	template <typename V, uint ID>
	inline void CLComputePass<V,ID>::EndBlock ()
	{
		string	log;
		log << "block (" << _currentBlockCoord.ToString() << ") time: ";
		StringUtils::TimeToStr( log, _timer.GetTime() - _processingBlockStartTime );

		DEBUG_CONSOLE( log.cstr() );

		_currentBlockCoord = ivec3();
	}
	
/*
=================================================
	OnError
=================================================
*/
	template <typename V, uint ID>
	inline bool CLComputePass<V,ID>::OnError (typename EProcessingError::type code) const
	{
		switch ( code )
		{
			case EProcessingError::NONE :
				return true;
		}
		return false;	// break on error
	}


}	// Voxels
